from urllib import parse as urllib_parse
from typing import Optional
import random
from datetime import timedelta

from django.db import models
from django.utils.translation import gettext, gettext_lazy as _
from django.utils import timezone as django_timezone
from django.core.exceptions import ValidationError
from django.shortcuts import reverse

from core.models import OptimizedUUIDModel
from utils.validators import root_http_url_validator


class AuthProviderType(models.TextChoices):
    AAI = 'aai', 'AAI'
    ORCID = 'orcid', 'ORCID'


class AuthProvider(OptimizedUUIDModel):
    name = models.CharField(verbose_name=_('名称'), max_length=254)
    provider_type = models.CharField(
        verbose_name=_('类型'), max_length=16,
        choices=AuthProviderType.choices, default=AuthProviderType.AAI.value
    )
    is_active = models.BooleanField(verbose_name=_('可用状态'), default=False)
    client_id = models.CharField(verbose_name=_('客户端id'), max_length=64)
    client_secret = models.CharField(verbose_name=_('客户端密钥'), max_length=128)
    endpoint_url = models.URLField(
        verbose_name=_('服务地址'), max_length=254,
        help_text=_('认证服务的基网址，例如：https://test.cn')
    )
    meta_url = models.CharField(
        verbose_name=_('Meta API path'), max_length=128,
        help_text=_('OAuth认证Meta API 的路径，如：ORCID的“/.well-known/openid-configuration”，'
                    'AAI的“/oidc/.well-known/openid-configuration”')
    )
    callback_url = models.URLField(
        verbose_name=_('云坤认证回调网址'), max_length=254, blank=True, default='',
        help_text=_('云坤认证服务对接此第三方认证时需要注册的回调网址，第三方认证服务认证后调回云坤认证的网址。'
                    '可以只填写一个根网址，保存时后台会自动填充完整回调网址')
    )
    created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True)
    description = models.CharField(verbose_name=_('描述'), max_length=254, blank=True, default='')

    class Meta:
        db_table = 'auth_pd_providers'
        ordering = ['-created_at']
        verbose_name = _('02_认证服务提供者')
        verbose_name_plural = verbose_name
        constraints = [
            models.UniqueConstraint(fields=('provider_type',), name='uni_auth_pd_provider_type'),
        ]

    def __str__(self):
        return f'{self.name} [{self.get_provider_type_display()}]'

    def get_metadata_url(self) -> str:
        meta_url = self.meta_url
        if meta_url.startswith("http"):
            return meta_url

        return urllib_parse.urljoin(self.endpoint_url, meta_url)

    def clean(self) -> None:
        try:
            url_path = reverse('auth_provider:auth_callback', kwargs={'provider_type': self.provider_type})
            self.callback_url = urllib_parse.urljoin(self.callback_url, url_path)
        except Exception:
            pass


class InternalUser(OptimizedUUIDModel):
    email = models.CharField(verbose_name=_('邮箱'), max_length=128)
    true_name = models.CharField(verbose_name=_('姓名'), max_length=128, blank=True, default='')
    org_name = models.CharField(verbose_name=_('公司'), max_length=254, blank=True, default='')
    is_active = models.BooleanField(verbose_name=_('激活状态'), default=True)
    created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True)
    description = models.CharField(verbose_name=_('描述'), max_length=254, blank=True, default='')

    class Meta:
        db_table = 'auth_pd_internaluser'
        ordering = ['-created_at']
        verbose_name = _('内部用户')
        verbose_name_plural = verbose_name
        constraints = [
            models.UniqueConstraint(fields=('email',), name='uni_auth_pd_internaluser_email'),
        ]

    def __str__(self):
        return self.email


class ExternalUser(OptimizedUUIDModel):
    """
    第三方认证服务用户信息
    """
    provider_type = models.CharField(
        verbose_name=_('认证服务类型'), max_length=16,
        choices=AuthProviderType.choices, default=AuthProviderType.AAI.value
    )
    external_id = models.CharField(
        verbose_name=_('外部用户id'),
        max_length=64,
        help_text=_('第三方认证服务用户id')
    )
    email = models.CharField(verbose_name=_('邮箱'), max_length=128, blank=True, default='')
    true_name = models.CharField(verbose_name=_('姓名'), max_length=128, blank=True, default='')
    org_name = models.CharField(verbose_name=_('公司'), max_length=254, blank=True, default='')
    internal_user = models.ForeignKey(
        to=InternalUser,
        verbose_name=_('内部用户'),
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        default=None,
        help_text=_('第三方认证服务用户绑定的内部用户')
    )
    created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True)

    class Meta:
        db_table = 'auth_pd_externaluser'
        ordering = ['-created_at']
        verbose_name = _('外部用户')
        verbose_name_plural = verbose_name
        constraints = [
            models.UniqueConstraint(fields=('external_id', 'provider_type'), name='uni_auth_pd_extuser_id_provider_type'),
        ]

    def __str__(self):
        return f'{self.external_id} [{self.provider_type}]'


class AuthCode(OptimizedUUIDModel):
    internal_user = models.ForeignKey(
        to=InternalUser,
        verbose_name=_('内部用户'),
        on_delete=models.CASCADE,
        related_name='+'
    )
    used = models.BooleanField(verbose_name=_('已使用'), default=False)
    expired_at = models.DateTimeField(verbose_name=_('过期时间'))
    created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)

    class Meta:
        db_table = 'auth_pd_authcode'
        ordering = ['-created_at']
        verbose_name = _('授权码')
        verbose_name_plural = verbose_name

    def set_used(self):
        self.used = True
        self.save(update_fields=['used'])


class AllowedRedirect(OptimizedUUIDModel):
    class PatternType(models.TextChoices):
        EXACT = 'exact', _('精确匹配')
        WILDCARD = 'wildcard', _('通配符匹配')

    name = models.CharField(verbose_name=_('服务名称'), max_length=254)
    pattern = models.CharField(
        verbose_name=_('匹配规则'), max_length=254,
        help_text=_('服务的回调地址域名或IP，也支持域名通配符格式“*.example.com”，IP网段CIDR格式“192.168.1.0/24”')
    )
    pattern_type = models.CharField(
        verbose_name=_('匹配类型'), max_length=16,
        choices=PatternType.choices, default=PatternType.EXACT.value,
        help_text=_('匹配规则为通配符格式和IP网段CIDR格式时，需要选择“通配符匹配”')
    )
    is_active = models.BooleanField(verbose_name=_('可用状态'), default=False)
    description = models.CharField(verbose_name=_('描述'), max_length=254, blank=True, default='')
    create_user = models.CharField(verbose_name=_('创建人'), max_length=128, blank=True, default='')
    created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True)

    class Meta:
        db_table = 'auth_pd_allowedredirect'
        ordering = ['-created_at']
        verbose_name = _('01_回调地址白名单')
        verbose_name_plural = verbose_name


class AuthRecord(OptimizedUUIDModel):
    class AuthStatus(models.TextChoices):
        INITIATED = 'init', _('已发起认证')
        AUTHORIZED = 'auth', _('第三方已认证')
        COMPLETED = 'completed', _('完成授权')

    redirect_uri = models.CharField(verbose_name=_('请求回调地址'), max_length=254)
    remote_ip = models.CharField(verbose_name=_('客户端IP'), max_length=39, blank=True, default='')
    hostname = models.CharField(verbose_name=_('服务域名'), max_length=128, blank=True, default='')
    provider_type = models.CharField(
        verbose_name=_('类型'), max_length=16,
        choices=AuthProviderType.choices, default=AuthProviderType.AAI.value
    )
    internal_user = models.ForeignKey(
        verbose_name=_('内部用户'),
        to=InternalUser,
        on_delete=models.SET_NULL,
        null=True, blank=True, default=None
    )
    email = models.CharField(verbose_name=_('邮箱'), max_length=128, blank=True, default='')
    auth_status = models.CharField(
        verbose_name=_('认证状态'), max_length=16,
        choices=AuthStatus.choices, default=AuthStatus.INITIATED.value
    )

    created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True)

    class Meta:
        db_table = 'auth_pd_authrecord'
        ordering = ['-created_at']
        verbose_name = _('认证记录')
        verbose_name_plural = verbose_name

    def set_auth_status(
            self,
            auth_status: str,
            internal_user: Optional[InternalUser],
            email: str = ''
    ):
        self.auth_status = auth_status
        if internal_user:
            self.internal_user = internal_user

        if email:
            self.email = email

        self.save(update_fields=['auth_status', 'email', 'internal_user', 'updated_at'])

    def set_auth_status_completed(
            self, internal_user: Optional[InternalUser],
            email: str = '',
            raise_exception: bool = True
    ):
        try:
            self.set_auth_status(
                auth_status=self.AuthStatus.COMPLETED.value,
                internal_user=internal_user,
                email=email
            )
        except Exception:
            if raise_exception:
                raise

    def set_auth_status_authorized(
            self, internal_user: Optional[InternalUser],
            email: str = '',
            raise_exception: bool = True
    ):
        try:
            self.set_auth_status(
                auth_status=self.AuthStatus.AUTHORIZED.value,
                internal_user=internal_user,
                email=email
            )
        except Exception:
            if raise_exception:
                raise


class EmailVerificationCode(OptimizedUUIDModel):
    external_user = models.ForeignKey(
        to=ExternalUser,
        verbose_name=_('外部用户'),
        on_delete=models.CASCADE,
        related_name='+'
    )
    code = models.CharField(verbose_name=_('验证码'), max_length=8)
    email = models.CharField(verbose_name=_('邮箱'), max_length=128)
    expired_at = models.DateTimeField(verbose_name=_('过期时间'))
    created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)

    class Meta:
        db_table = 'auth_pd_email_vericode'
        ordering = ['-created_at']
        verbose_name = _('邮箱验证码')
        verbose_name_plural = verbose_name

    def __str__(self):
        return f'{self.email} [{self.code}]'

    @staticmethod
    def build_a_code() -> str:
        return ''.join(random.choices('0123456789', k=6))

    @classmethod
    def create_code(cls, external_user: ExternalUser, email: str, expired_minutes: int = 10) -> 'EmailVerificationCode':
        nt = django_timezone.now()

        # 删除邮箱旧验证码和删除过期验证码
        cls.objects.filter(
            models.Q(email=email) | models.Q(expired_at__lte=nt)
        ).delete()

        obj = cls(
            external_user=external_user,
            email=email,
            code=cls.build_a_code(),
            expired_at=nt + timedelta(minutes=expired_minutes)
        )
        obj.save(force_insert=True)
        return obj


class AuthConfig(models.Model):
    class ConfigName(models.TextChoices):
        AUTH_BASE_URL = 'auth_base_url', _('本认证服务地址')
        JWT_PRIVATE_KEY = 'jwt_private_key', _('JWT签名加密私钥')
        JWT_PUBLIC_KEY = 'jwt_public_key', _('JWT签名加密公钥')

    # 配置的默认值，自动创建配置参数记录时填充的默认值
    value_defaults = {
        ConfigName.AUTH_BASE_URL.value: {
            'value': 'https://{hostname}.cstcloud.net/', 'remark': '本认证服务的地址'
        },
        ConfigName.JWT_PRIVATE_KEY.value: '',
        ConfigName.JWT_PUBLIC_KEY.value: '',
    }

    id = models.BigAutoField(primary_key=True)
    name = models.CharField(verbose_name=_('配置名称'), max_length=32, choices=ConfigName.choices)
    value = models.TextField(verbose_name=_('配置内容'), default='')
    remark = models.CharField(verbose_name=_('备注'), max_length=254, blank=True, default='')
    created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True)

    class Meta:
        db_table = 'auth_pd_config'
        ordering = ['created_at']
        verbose_name = _('00_云坤认证参数')
        verbose_name_plural = verbose_name
        constraints = [
            models.UniqueConstraint(fields=('name',), name='uni_auth_pd_config_name')
        ]

    def __str__(self):
        return self.name

    def clean(self):
        if self.name == self.ConfigName.AUTH_BASE_URL.value:
            try:
                root_http_url_validator(self.value)
            except ValidationError:
                raise ValidationError(message={'value': gettext('不是一个有效的基网址')})
